《Effective Java》第二章:创建和销毁对象
本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时得销毁,以及如何管理对象和销毁之前必须进行的各种清理动作。
第1条:考虑用静态工厂方法代替构造器
这条感觉就是推荐我们尽量使用静态方法来生成实例对象。
1 | public static Boolean valueOf(boolean b) |
推荐的理由如下:
- 静态构造方法有自己的名称。(这理由是不是有点。。,个人感觉绝大部分程序猿在创建对象时都是首先尝试
new Construct()
) - 不必在每次调用它们时都创建一个新的对象。(单例中比较常用吧)
- 它们可以返回原返回类型的任何子类型的对象。(这个特征的确是比较有优势一点)
- 在创建参数化类型实例的时候,它们使代码变得更加简洁。
1 | //普通是需要这么干的 |
当然使用这种静态方法也有缺点:
- 类如何不含有公有的或者受保护的构造器,就不能子类化。
- 它们与其他的静态方法实际上没有任何区别。
一些静态工厂方法的惯用名称(也的确常见):
- ValueOf
- of
- getInstance
- newInstance
- getType
- newType
第2条:遇到多个构造器参数的时候要考虑用构建器
当你的Class
有多个自定义参数需要初始化的时候-_-
重叠构造器
你可能会使用重叠构造器来编写代码:
1 | class Test{ |
但是当需要初始化的参数实在太多的时候,这个Class
的构造函数很快就会失去控制,而且在调用构造器的时候也会因为要初始化的参数太多而弄混。
使用setter方法来设置参数
即JavaBean
模式,在这种模式下调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个可选参数。
1 | class Test{ |
这种模式弥补重叠构造器模式的不足,创建对象实例很容易,并且代码读起来也很容易:
1 | Test test=new Test(); |
但是它有一个严重的缺点,就是这个构造过程被分到几个不同的调用中时,该对象可能处于不一致的状态。-_-
使用Builder模式
它既能保证那重叠构造器那样安全,也能保证像JavaBean
那样有好的可读性。
1 | class Test{ |
这种模式其实是生成一个内部类,在内部类中通过setter
方法设置相应的字段,然后调用build
方法生成真正需要的Class
实例。
1 | Test test=new Test.Builder() |
这个方法十分灵活,可以通过构建一个builder来构建多个对象,builder的参数可以在构建期间进行调整,也可以随不同的对象而改变。
此时,我们可以将这个builder定义为一个通用的接口
1 | public interface Builder<T>{ |
然后可以通过创建接口来创建更多的实例对象
1 | Builder<Test> builderTest=new Test.Builder() |
Builder模式也有不足之处,为了创建对象,必须创建它的构建器,虽然创建构建器的开销在实践中不是那么明显,但是在某些十分注重性能的情况下,可能会成功问题。
简而言之,如果累的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式模式是一个不错的选择。
第3条:用私有的构造器或者枚举类型强化Singleton属性
单例模式估计是大家在《设计模式》中最早接触的一种,也是较为常用的一种模式,从它的线程安全性和运行效率性上考虑,我们所了解的应该有这么几种类型的单例写法1:
- 懒汉式单例
- 饿汉式单例
- 登记式单例
除了上述,书本极力推荐的一种是单元素枚举类型
1 |
|
该方法无偿的提供了序列化机制,绝对防止多次实例化。但是由于为了使用单例将”类”改为了”枚举”,这样就是导致丢掉一些类的特征,比如说继承,因为枚举是默认继承java.lang.Enum
。
第4条:通过私有构造器强化不可实例化的能力
当你的类里面只包含静态方法和静态变量时(比如说工具类),那么请在该类上添加一个私有的构造器,这样可以保护该类,同时也不会误导用户。
当然这么写有个副作用就是这个类就不能被继承了,子类就没有访问超类的构造器可用了。
第5条:避免创建不必要的对象
一般来说,最好能重用对象而不是在每次需要的时候就创建一个项目功能的新对象。
1 |
|
几大重用的关键点:
- 对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法,以免创建不必要的对象。
- 可以重用那些已知不会被修改的可变对象。
- 优先使用基本类型而不是装箱类型。
本条目并不是暗示“创建对象的代价非常昂贵,我们应该要尽量避免对象的创建”
第6条:消除过期的引用
这条就是描述过期的引用会存在内存泄露的问题,那我们程序猿该如何处理呢?
类要是自己管理内存,程序猿就应该警惕内存泄露问题。
1 | /** |
上述代码是一个简单的栈结构的实现,但是在元素被多次pop()
之后,Object[]
里面索引大于size
的对象不在可用,但是由于他们还是存储在数组中,所以垃圾回收机制不会处理这些对象,最终会造成内存泄露。
可以用下面的方法来修复:
1 | public Object pop() |
上述只是一个特征,这条并不是教我们对于每个对象引用不再用到时就将它清空,其实没有这个必要,这样做会把代码弄得很乱,清除过期引用最好的方法是让包含该引用变量结束生命周期。
内存泄露另一种常见的来源是缓存
当对象放入缓存中很容易被遗忘,然而它会长期存储在内存中,这样可以使用WeakHashMap
来代表缓存,当缓存的项过期之后,他们会被自动删除。
也可以使用LinkedHashMap
来实现LRU缓存,当容量满时会删除最久一个没有用过的项。
内存泄露的第三个常见的来源是监听器和其他回调
比如说你注册了某些Api的回调,但是没有显示的取消注册,这样他们会越积越多。~~~~
第7条:避免终结方法
终结方法(
finalizer
)通常是不可预测的,也是很危险的,一般情况下是不必要的。
避免的原因有如下几个:
不能保证被及时的执行。
因为进行gc
时终结方法的优先级一般比其他的要低,注重时间的任务不应该用终结方法来完成,比如在finalizer
中关闭已打开的文件JVM不会保证他们会被执行。
不应该依赖终结方法来更新中重要的持久状态。终结方法可能会有非常重要的性能损失。
所以如果在自己的类中真实的需要将对象终止,则自己最好提供一个显示的终止方法,并且要求编码人员再不需要该对象时进行显示掉调用终止方法,比如file.close
当然终结方法也不是一无是处:
- 当对象的所有者忘记调用前面段落中建议的显示终止方法时,可以用终止方法在日志中记录记录警告或者再显示的调用该终止方法
- 与对象的本地对等体有关。大概是普通对象通过
native method
委托给一个本地对象,而这个本地对象时不受jVM
管理的。但是可以使用终结方法来完成必要资源释放,它可以是本地方法,也可以条用本地方法。
注意:
finalizer
方法链不会自动的执行,所以在自定义finalizer
时最好显示得调用super.finalizer()
参考
本作品采用[知识共享署名-非商业性使用-相同方式共享 2.5]中国大陆许可协议进行许可,我的博客欢迎复制共享,但在同时,希望保留我的署名权kubiCode,并且,不得用于商业用途。如您有任何疑问或者授权方面的协商,请给我留言。